using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

using Peradigm.Framework.Domain.Parties;
using Peradigm.Framework.Domain.Security;
using Peradigm.Framework.Persistence;

using Liberty.Domain.ApplicationManagement;
using Liberty.Domain.CaseManagement;
using Liberty.Domain.DocumentManagement;
using Liberty.Domain.GrantManagement;
using Liberty.Domain.ApprovalManagement;
using Liberty.Domain.Kernel; 
using Liberty.Domain.NoteManagement;



namespace Liberty.Domain.GrantManagement
{
    public enum GrantStateOID
    {
        Opened = 1,
        Assigned = 2,
        Validated = 3,
        Approved = 4,
        Dispersed = 5,
        AwaitingApproval = 6,
        AwaitingValidation = 7,
        AwaitingDispersal = 8,
        AwaitingFundMgrApproval = 9
    }

    [Serializable]
    public class Grant : PersistentModel, INotable
    {
        
        #region Constants
        private const string
            NOTE_APPROVAL = "User initiated approval.",
            NOTE_DISPERSAL = "User initiated dispersal.",
            NOTE_VERIFICATION = "User initiated verification.";
        private const int MINIMUM_APPROVALS_TO_DISPERSE = 2;
        #endregion

        #region Event Handlers
        #endregion

        #region Methods
        #region Accessing
        #endregion

        #region Adding & Removing
        public Note AddNote( Note newNote )
        {
            this.primNotes.Add( newNote );
            return newNote;
        }

        public Note RemoveNote( Note existingNote )
        {
            if( this.primNotes.Contains( existingNote ) )
            {
                this.primNotes.Remove( existingNote );
            }
            return existingNote;
        }

        public void RemoveNotes()
        {
            this.primNotes.Clear();
        }

        public Event AddEvent( Event newEvent )
        {
            this.primEvents.Add( newEvent );
            return newEvent;
        }

        public Event RemoveEvent( Event existingEvent )
        {
            if( this.primEvents.Contains( existingEvent ) )
            {
                this.primEvents.Remove( existingEvent );
            }
            return existingEvent;
        }

        public void RemoveEvents()
        {
            this.primEvents.Clear();
        }
        #endregion

        #region Workflow
#warning Fix with IoC?
        public Grant InitiateApplicationBy( User worker )
        {
            Event creation = new Event( this.ActivityFor( Activity.ACTIVITY_CREATION ), DateTime.Now, worker );
            this.primEvents.Add( creation );
            return this;
        }

        public Grant ApproveBy( User worker )
        {
            Event approval = new Event( this.ActivityFor( Activity.ACTIVITY_APPROVAL ), DateTime.Now, worker );
            approval.Note = NOTE_APPROVAL;
            this.primEvents.Add( approval );
            return this;
        }

        public int NumApprovalsToDisperse()
        {
            return ( ( this.Value < 2000 ) ? 1 : 2 );
        }

        public bool IsValidated()
        {
            return ( this.NumApprovals() == 1 );
        }

        public bool WasApprovedBy( User worker )
        {
            List<Event> events = this.primEvents.FindAll( item => ( item.CreatedBy.Guid == worker.Guid ) &&
                                                                  ( item.Activity.Equals( this.ActivityFor( Activity.ACTIVITY_APPROVAL ) ) ) );
            return events.Count > 0;
        }

        /// <summary>
        /// Indicates whether or not the grant was approved by someone with the specified role.
        /// </summary>
        /// <param name="roleName">Name of the role in question</param>
        /// <returns></returns>
        public bool WasApprovedBy( string roleName )
        {
            List<Event> events = this.primEvents.FindAll( item => item.CreatedBy.IsInRole( roleName ) &&
                                                                  ( item.Activity.Equals( this.ActivityFor( Activity.ACTIVITY_APPROVAL ) ) ) );
            return events.Count > 0;
        }

        public Grant DisperseBy( User worker )
        {
            Event dispersal = new Event( this.ActivityFor( Activity.ACTIVITY_DISPERSAL ), DateTime.Now, worker );
            dispersal.Note = NOTE_DISPERSAL;
            this.primEvents.Add( dispersal );
            return this;
        }

        public bool IsDispersable()
        {
            List<Event> events = this.primEvents.FindAll( item => ( item.Activity.Equals( this.ActivityFor( Activity.ACTIVITY_APPROVAL ) ) ) );
            return !this.WasDispersed() && ( events.Count >= this.NumApprovalsToDisperse() );
        }

        public bool IsDispersableBy( User worker )
        {
            List<Event> events = this.primEvents.FindAll( item => ( item.CreatedBy.Guid != worker.Guid ) &&
                                                                  ( item.Activity.Equals( this.ActivityFor( Activity.ACTIVITY_APPROVAL ) ) ) );
            return this.IsDispersable() && ( events.Count >= this.NumApprovalsToDisperse() );
        }

        public bool WasDispersed()
        {
            List<Event> events = this.primEvents.FindAll( item => ( item.Activity.Equals( this.ActivityFor( Activity.ACTIVITY_DISPERSAL ) ) ) );
            return events.Count > 0;
        }

        public bool WasDispersedBy( User worker )
        {
            List<Event> events = this.primEvents.FindAll( item => ( item.CreatedBy.Guid == worker.Guid ) &&
                                                                  ( item.Activity.Equals( this.ActivityFor( Activity.ACTIVITY_DISPERSAL ) ) ) );
            return events.Count > 0;
        }

        public Grant VerifyBy( User worker )
        {
            Event verification = new Event( this.ActivityFor( Activity.ACTIVITY_VERIFICATION ), DateTime.Now, worker );
            verification.Note = NOTE_VERIFICATION;
            this.primEvents.Add( verification );
            return this;
        }
        #endregion
        #endregion

        #region Properties
        public Case Case
        {
            get
            {
                return i_Case;
            }
            set
            {
                i_Case = value;
            }
        }

        public Person Custodian
        {
            get
            {
                return i_Custodian;
            }
            set
            {
                i_Custodian = value;
            }
        }

        public Liberty.Domain.Kernel.Address CustodiansAddress
        {
            get
            {
                Liberty.Domain.Kernel.Address custodiansAddress = null;
                try
                {
                    custodiansAddress = this.Custodian.AddressFor( TypeOfContactPoint.DESCRIPTOR_ALTERNATIVE ) as Liberty.Domain.Kernel.Address;
                }
                catch
                {
                }

                return custodiansAddress;
            }
            set
            {
                this.Custodian.AddAddress( TypeOfContactPoint.DESCRIPTOR_ALTERNATIVE, value );
            }
        }

        public int CheckNumber
        {
            get
            {
                return i_CheckNumber;
            }
            set
            {
                i_CheckNumber = value;
            }
        }

        public String NameOnCreditCard
        {
            get
            {
                return i_NameOnCreditCard;
            }
            set
            {
                i_NameOnCreditCard = value;
            }
        }

        public DateTime CreatedDate
        {
            get
            {
                return i_CreatedDate;
            }
            set
            {
                i_CreatedDate = value;
            }
        }

        public DeliveryMethod DeliveryMethod
        {
            get
            {
                return i_DeliveryMethod;
            }
            set
            {
                i_DeliveryMethod = value;
            }
        }

        public PaymentType PaymentType
        {
            get
            {
                return i_PaymentType;
            }
            set
            {
                i_PaymentType = value;
            }
        }

        public Liberty.Domain.Kernel.Address ApplicantsAddress
        {
            get
            {
                return this.Applicant.AddressFor( TypeOfContactPoint.DESCRIPTOR_OTHER ) as Liberty.Domain.Kernel.Address;
            }
            set
            {
                this.Applicant.AddAddress( TypeOfContactPoint.DESCRIPTOR_OTHER, value );
            }
        }

        public GrantState Status
        {
            get
            {
                return this.PrimStatus();
            }
        }

        public GrantType Type
        {
            get
            {
                return i_Type;
            }
            set
            {
                i_Type = value;
            }
        }

        public decimal Value
        {
            get
            {
                return i_Value;
            }
            set
            {
                i_Value = value;
            }
        }

        public IEnumerable<Document> Documents
        {
            get
            {
                return i_Documents;
            }
        }

        public IEnumerable<Event> Events
        {
            get
            {
                return this.primEvents;
            }
        }

        public IEnumerable<Note> Notes
        {
            get
            {
                return this.primNotes;
            }
        }

        public Applicant Applicant
        {
            get
            {
                return i_Applicant;
            }
            set
            {
                i_Applicant = value;
            }
        }

        public Charity Charity
        {
            get
            {
                return i_Charity;
            }
            set
            {
                i_Charity = value;
            }
        }

        public Task Task
        {
            get
            {
                return i_Task;
            }
            set
            {
                i_Task = value;
            }
        }
        #endregion

        #region Private Methods
        private int NumApprovals()
        {
            List<Event> events = this.primEvents.FindAll( item => ( item.Activity.Equals( this.ActivityFor( Activity.ACTIVITY_APPROVAL ) ) ) );
            return events.Count;
        }

        private bool RequiresValidation()
        {
            return false; // Grants no longer require validation.
        }

        private bool RequiresValidationByFundManager()
        {
            return ( this.Value >= 5001 );
        }

        private bool IsInAwaitingValidationState()
        {
            return ( this.NumApprovals() == 1 ) && ( this.RequiresValidation() );
        }

        private bool IsInAwaitingFundMgrApprovalState()
        {
            return ( this.NumApprovals() == 1 ) && ( this.RequiresValidationByFundManager() );
        }

        private bool IsInAwaitingDispersalState()
        {
            return ( this.NumApprovals() == 1 && !this.RequiresValidation() && !this.RequiresValidationByFundManager() )
                   ||
                   ( this.NumApprovals() == 2 );
        }
        
        private Activity ActivityFor( string description )
        {
            return new Activity( PersistentModel.NEW_OID, PersistentModel.NEW_VERSION, description, ReferenceValue.DEFAULT_RANKING, ReferenceValue.DEFAULT_STATUS );
        }

        private GrantState PrimStatus()
        {
            GrantState state = Grant.GrantStatesByOid[GrantStateOID.AwaitingApproval];
            foreach ( DetermineGrantStateHandler stateHandler in this.GrantStateHandlerMap.Keys )
            {
                if ( stateHandler() )
                {
                    state = this.GrantStateHandlerMap[stateHandler];
                    break;
                }
            }
            return state;
        }
        private void BuildGrantStateHandlerMap()
        {
            this.GrantStateHandlerMap = new Dictionary<DetermineGrantStateHandler, GrantState>();
            this.GrantStateHandlerMap.Add( new DetermineGrantStateHandler( this.WasDispersed ), Grant.GrantStatesByOid[GrantStateOID.Dispersed] );
            this.GrantStateHandlerMap.Add( new DetermineGrantStateHandler( this.IsInAwaitingDispersalState ), Grant.GrantStatesByOid[GrantStateOID.AwaitingDispersal] );
            this.GrantStateHandlerMap.Add( new DetermineGrantStateHandler( this.IsInAwaitingFundMgrApprovalState ), Grant.GrantStatesByOid[GrantStateOID.AwaitingFundMgrApproval] );
            this.GrantStateHandlerMap.Add( new DetermineGrantStateHandler( this.IsInAwaitingValidationState ), Grant.GrantStatesByOid[GrantStateOID.AwaitingValidation] );
        }
        #endregion

        #region Private Properties
        private List<Event> primEvents
        {
            get
            {
                return i_Events;
            }
            set
            {
                i_Events = value;
            }
        }

        private List<Note> primNotes
        {
            get
            {
                return i_Notes;
            }
            set
            {
                i_Notes = value;
            }
        }

        private Dictionary<DetermineGrantStateHandler, GrantState> GrantStateHandlerMap { get; set; }
        private static Dictionary<GrantStateOID, GrantState> GrantStatesByOid { get; set; }
        #endregion

        #region Construction and Finalization
        /// <summary>
        /// Creates a stubbed grant populated with frequently used primitives.
        /// Other aggregated objects are not populated by this factory method.
        /// </summary>
        /// <returns></returns>
        public static Grant TestInstance()
        {
            Grant stub = new Grant();
            stub.DeliveryMethod = DeliveryMethod.TestInstance();
            stub.Oid = 12L;
            stub.PaymentType = PaymentType.TestInstance();
            stub.Type = GrantType.TestInstance();
            stub.Value = 100000000.00M;

            return stub;
        }

        public static void BuildGrantStatesByOidListFrom( List<GrantState> grantStates )
        {
            Grant.GrantStatesByOid = new Dictionary<GrantStateOID, GrantState>();
            grantStates.ForEach( grantState => Grant.GrantStatesByOid.Add( (GrantStateOID)grantState.Oid, grantState ) );
        }

        public Grant() : this( Grant.NEW_OID, Grant.NEW_VERSION, default( decimal ), null, null, null, null, null, DateTime.Now )
        {
        }

        public Grant( long oid, byte[] version, decimal value, GrantType type,
            PaymentType paymentType, DeliveryMethod deliveryMethod, Charity organization, Applicant applicant,
            DateTime createdDate )
            : base( oid, version )
        {
            this.Value = value;
            this.Type = type;
            this.PaymentType = paymentType;
            this.DeliveryMethod = deliveryMethod;
            this.Charity = organization;
            this.Applicant = applicant;
            this.CreatedDate = createdDate;
            this.BuildGrantStateHandlerMap();
        }
        #endregion

        #region Data Elements
        private CaseManagement.Case i_Case;
        private Applicant i_Applicant;
        private Person i_Custodian;
        private DeliveryMethod i_DeliveryMethod;
        private PaymentType i_PaymentType;
        private GrantState i_Status;
        private GrantType i_Type;
        private decimal i_Value;
        private List<Document> i_Documents = new List<Document>();
        private List<Note> i_Notes = new List<Note>();
        private List<Event> i_Events = new List<Event>();
        private Charity i_Charity;
        private Task i_Task;
        private DateTime i_CreatedDate;
        private int i_CheckNumber;
        private String i_NameOnCreditCard;
        private delegate bool DetermineGrantStateHandler();
        #endregion
    }
}
